package com.rengwuxian.coursecoroutines._4_flow

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import kotlin.coroutines.EmptyCoroutineContext

/**
 * Flow的工作原理和应用场景
 * Flow的原理和本质、Channel和Flow的「冷」和「热」、collect()的「把协程卡死」
 *
 * 同一个flow，在多个协程collect，都可以拿到完整的flow发射的数据流。
 * Flow的原理非常简单，其实就是把flow发送数据地方，挪到调用collect()的地方来执行。
 * 比如：
 *   val numsFlow = flow {
 *     emit(1)
 *     delay(100)
 *     emit(2)
 *   }
 *   就可以放到collect()里，相当于，把处理代码替换了emit()的占位。
 *   scope.launch {
 *     numsFlow.collect {
 *       println("A: $it")// emit(1)
 *       delay(100)
 *       println("A: $it")// emit(2)
 *     }
 *   }
 *   当然也不是简单的替换，flow里面在emit的时候会检查CoroutineContext的切换，是不允许的。
 *
 * Channel: hot,有自己的生产逻辑，无论有没有消费者，都会启动生产流程。
 * Flow: cold,是一套数据生产规则，只有在被collect的时候，才会真正生产数据。（lazy）
 * 每次collect都会独立走一遍生产逻辑，故每次collect都会拥有完整的数据流。
 * flow的collect必须在协程里，但是flow的创建不需要，因为只是把生产流程包起来，而并不是真正的生产。
 *
 * 什么时候需要flow：
 * 需要同类型的依次到来的数据流，并且需要把数据的处理 和 数据使用的功能拆开
 * val weatherFlow = flow {
 *   while (true) {
 *     emit(getWeather())
 *     delay(60000)
 *   }
 * }
 *
 * scope.launch {
 *   weatherFlow.collect { // 把流程给堵住了
 *     println("Weather: $it")
 *   }
 *   XXXX// 走不到
 * }
 * collect后面的代码可能一直执行不到，把collect放在单独的协程里。
 * 或者可以在flow结束后，打印标记日志也可以。
 * Flow 并没有提供取消的方法，因为Flow是运行在协程中的我们可以依赖协程的运行与取消来实现 Flow 的取消。
 */
fun main() = runBlocking<Unit> {
  /*val numsFlow = flow {
    emit(1)
    delay(100)
    emit(2)
  }*/
  val weatherFlow = flow {
    while (true) {
      emit(getWeather())
      delay(600)
    }
  }
  val scope = CoroutineScope(EmptyCoroutineContext)
  scope.launch {
//    showWeather(weatherFlow)
    weatherFlow.collect {
      println("Weather: $it")
    }
    println("done")
    // log("done")
    /*numsFlow.collect {
      println("A: $it")
    }*/
  }
/*  scope.launch {
    delay(50)
    numsFlow.collect {
      println("B: $it")
    }
  }*/
  delay(2000)
  weatherFlow
  delay(10000)
}

val weatherFlow = flow {
  while (true) {
    emit(getWeather())
    delay(60000)
  }
}

suspend fun showWeather(flow: Flow<String>) {
  flow.collect {
    println("Weather: $it")
  }
}

suspend fun getWeather() = withContext(Dispatchers.IO) {
  "Sunny"
}